﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Animation;
using DiscoveryLogic.UI.Layouts.CustomEventArgs;

namespace DiscoveryLogic.UI.Layouts.Controls
{
    public delegate void ItemCrossedRingEventHandler(object sender, ItemCrossedRingEventArgs e);

    /// <summary>
    /// RadialPanel positions its children at angles around a central point at a given radius. It uses an
    /// attached property, Angle, to define the angle that each child is at, which is set automatically
    /// when the panel's arrangement is invalidated. The children are positioned an equal angle apart between
    /// a given start and end angle.
    /// The panel works best when RadialItemControl objects are placed within it, since the control has the concept
    /// of a viewable portion, and a label that is offset from the control, and uses the angle of the item to
    /// position the label. RadialItemControls will fire a Clicked event when they are clicked, and will allow
    /// for drag and drop of the controls on the panel. This enables re-arrangement of items on the panel
    /// or moving of a control from one panel to another (within the same container).
    /// </summary>
    public class RadialPanel : Panel
    {
        #region Constants and Enums

        /// <summary>
        /// DragState is used to depict whether a given control on the panel is fixed at a given angle,
        /// is being dragged by the user (so is freely positionable), or being animated (so is still
        /// freely positionable, but is not the item being dragged).
        /// </summary>
        public enum DragState
        {
            Fixed,
            Dragging,
            Animating
        }

        // The approximate number of pixels to reach the sun, assuming a 96dpi resolution monitor - this is used
        // as a maximum value when deciding which object the mouse is currently closed to
        private const double ApproxPixelsToSun = 3657600000000000.0;

        #endregion // Constants and Enums

        #region Constructors

        public RadialPanel()
        {
            // Initialise some variables that are used for drag and drop state
            _capturedMouse = false;
            _lastHilightedIndex = -1;
            _sourceObject = null;

            ScaleX = 1;
            ScaleY = 1;

            DragAndDropDistance = 50;
            DynamicAngleSelection = true;

            _registeredPanels = new List<RadialPanel>();
            _lastPanelUnderMouse = null;
            _draggedToPanel = null;

            // Hook up our mouse events to allow for drag and drop
            MouseLeftButtonDown += RadialPanel_MouseLeftButtonDown;
            MouseMove += RadialPanel_MouseMove;
            MouseLeftButtonUp += RadialPanel_MouseLeftButtonUp;
        }

        #endregion // Constructors

        #region Non-dependency Properties

        /// <summary>
        /// The last item that we could have dropped a dragged item. Items are "highlighted" by moving all the other
        /// items out of the way.
        /// </summary>
        public int LastHilightedIndex
        {
            get { return _lastHilightedIndex; }
            set { _lastHilightedIndex = value; }
        }

        #endregion // Non-dependency Properties

        #region Dependency Properties

        /// <summary>
        /// Radius - a Point property that holds the X and Y distances for the radius of the ellipse
        /// to position the children at
        /// </summary>
        public static readonly DependencyProperty RadiusProperty =
            DependencyProperty.Register("Radius", typeof(Point), typeof(RadialPanel), new PropertyMetadata(InvalidateLayoutCallback));

        /// <summary>
        /// Center - a Point property that holds the X,Y co-ordinates inside the panel of the centre of the ellipse
        /// to position the children at
        /// </summary>
        public static readonly DependencyProperty CenterProperty =
            DependencyProperty.Register("Center", typeof(Point), typeof(RadialPanel), new PropertyMetadata(InvalidateLayoutCallback));

        /// <summary>
        /// AngleStart - a double that represents the number of degrees at which we start to position the children.
        /// The first child item will be positioned at this angle.
        /// </summary>
        public static readonly DependencyProperty AngleStartProperty =
            DependencyProperty.Register("AngleStart", typeof(double), typeof(RadialPanel), new PropertyMetadata(InvalidateLayoutCallback));

        /// <summary>
        /// AngleEnd - a double that represents the number of degrees at which we stop positioning the children.
        /// The last child item will be positioned at this angle, unless both AngleStart=0 and AngleEnd=360, in which case
        /// we assume there is an extra "invisible" item, so that the last child item does not overlap the first.
        /// </summary>
        public static readonly DependencyProperty AngleEndProperty =
            DependencyProperty.Register("AngleEnd", typeof(double), typeof(RadialPanel), new PropertyMetadata(InvalidateLayoutCallback));
            
        /// <summary>
        /// MaxItems - the maximum number of items that can be contained on this panel. This is only used when items
        /// are being dragged between panels. If the XAML specifies 12 child items, when this property is only 5, all 12
        /// items will still be displayed, and positioned around the panel's positionin ellipse.
        /// </summary>
        public static readonly DependencyProperty MaxItemsProperty =
            DependencyProperty.Register("MaxItems", typeof(int), typeof(RadialPanel), null);

        /// <summary>
        /// ScaleX - a property that is used to reflect the scale of the parent container for the panel. This needs to be set otherwise
        /// drag and drop doesn't work correctly, as we need to position the item being dragged whilst being sensitive to the parent't
        /// scale transform.
        /// </summary>
        public static readonly DependencyProperty ScaleXProperty =
            DependencyProperty.Register("ScaleX", typeof(double), typeof(RadialPanel), null);

        /// <summary>
        /// ScaleY - a property that is used to reflect the scale of the parent container for the panel. This needs to be set otherwise
        /// drag and drop doesn't work correctly, as we need to position the item being dragged whilst being sensitive to the parent't
        /// scale transform.
        /// </summary>
        public static readonly DependencyProperty ScaleYProperty =
            DependencyProperty.Register("ScaleY", typeof(double), typeof(RadialPanel), null);

        /// <summary>
        /// DragAndDropDistance - a property that reflects how close we must be to a given item on the panel, before we decide we
        /// are dropping the item on that position. The larger this value is, the easier it is to drag and drop items in new positions
        /// </summary>
        public static readonly DependencyProperty DragAndDropDistanceProperty =
            DependencyProperty.Register("DragAndDropDistance", typeof (double), typeof (RadialPanel), null);

        /// <summary>
        /// DynamicAngleSelection - if true, then items are distributed round the panel based on the number of items currently on the panel.
        /// If false, the items are distributed as if the maximum number of items are on the panel, meaning that the angle between items
        /// is always the same, regardless of how many items are on the panel. Set to true by default
        /// </summary>
        public static readonly DependencyProperty DynamicAngleSelectionProperty =
            DependencyProperty.Register("DynamicAngleSelection", typeof(bool), typeof(RadialPanel), new PropertyMetadata(InvalidateLayoutCallback));

        /// <summary>
        /// Angle - an attached property that represents the angle, in degrees, to position child items at. This is set
        /// automatically when the panel's arrangement is invalidated.
        /// </summary>
        public static readonly DependencyProperty AngleProperty =
            DependencyProperty.RegisterAttached("Angle", typeof(double), typeof(RadialPanel), null);

        /// <summary>
        /// Dragging - an attached property that depicts whether a given child item is fixed at a given angle, being dragged
        /// (and so freely positionable), or being animated (and so temporarily freely positionable). This is more for
        /// internal state of the panel, than for user use, since it is asserted during drag and drop operations.
        /// </summary>
        public static readonly DependencyProperty DraggingProperty =
            DependencyProperty.RegisterAttached("Dragging", typeof (DragState), typeof (RadialPanel), null);

        /// <summary>
        /// Left - an attached property that depicts the X co-ordinate of the given child item within the panel. This is used
        /// for drag and drop, although during panel arrangement, this property will be maintained to indicate the X co-ordinate
        /// of the item at its given angle. It is also used for animation.
        /// </summary>
        public static readonly DependencyProperty LeftProperty =
            DependencyProperty.RegisterAttached("Left", typeof (double), typeof (RadialPanel), null);

        /// <summary>
        /// Top - an attached property that depicts the Y co-ordinate of the given child item within the panel. This is used
        /// for drag and drop, although during panel arrangement, this property will be maintained to indicate the Y co-ordinate
        /// of the item at its given angle. It is also used for animation.
        /// </summary>
        public static readonly DependencyProperty TopProperty =
            DependencyProperty.RegisterAttached("Top", typeof(double), typeof(RadialPanel), null);

        /// <summary>
        /// DefaultLeft - an attached property that depicts the X co-ordinate of where an item would normally be for the given
        /// Angle property. This is to make it easy to animate objects back to their default location.
        /// </summary>
        public static readonly DependencyProperty DefaultLeftProperty =
            DependencyProperty.RegisterAttached("DefaultLeft", typeof(double), typeof(RadialPanel), null);

        /// <summary>
        /// DefaultTop - an attached property that depicts the Y co-ordinate of where an item would normally be for the given
        /// Angle property. This is to make it easy to animate objects back to their default location.
        /// </summary>
        public static readonly DependencyProperty DefaultTopProperty =
            DependencyProperty.RegisterAttached("DefaultTop", typeof(double), typeof(RadialPanel), null);

        // An issue in Silverlight 2 beta 2 meant that Attached properties could not be animated. The following 3 properties
        // are used as part of a workaround to try and animate the children items as a control is dragged on the container

        /// <summary>
        /// AnimateStep is how far through the current reposition animation we are. This is the property that will be animated
        /// which will cause the new positions to be re-evaluated and updated.
        /// </summary>
        public static readonly DependencyProperty AnimateStepProperty =
            DependencyProperty.Register("AnimateStep", typeof (double), typeof (RadialPanel),
                                        new PropertyMetadata(DoAnimationCallback));

        /// <summary>
        /// SourcePoint is the start point for each item to be moved from. It only affects items whose Dragging
        /// property is set to Animated, when we use AnimateStep to animate items out of the way of the dragged object.
        /// </summary>
        public static readonly DependencyProperty SourcePointProperty =
            DependencyProperty.RegisterAttached("SourcePoint", typeof (Point), typeof (RadialPanel), null);

        /// <summary>
        /// TargetPoint is the end point for each item (to be moved to). It only affects items whose Dragging property
        /// is set to Animated, when we use AnimateStep to animate items out of the way of the dragged object.
        /// </summary>
        public static readonly DependencyProperty TargetPointProperty =
            DependencyProperty.RegisterAttached("TargetPoint", typeof (Point), typeof (RadialPanel), null);

        // Property getter / setters for dependency properties
        public Point Radius
        {
            get { return (Point) GetValue(RadiusProperty); }
            set { SetValue(RadiusProperty, value); }
        }
        public Point Center
        {
            get { return (Point) GetValue(CenterProperty); }
            set { SetValue(CenterProperty, value); }
        }
        public double AngleStart
        {
            get { return (double) GetValue(AngleStartProperty); }
            set { SetValue(AngleStartProperty, value); }
        }
        public double AngleEnd
        {
            get { return (double) GetValue(AngleEndProperty); }
            set { SetValue(AngleEndProperty, value); }
        }
        public int MaxItems
        {
            get { return (int) GetValue(MaxItemsProperty); }
            set { SetValue(MaxItemsProperty, value); }
        }

        public double ScaleX
        {
            get { return (double) GetValue(ScaleXProperty); }
            set { SetValue(ScaleXProperty, value); }
        }

        public double ScaleY
        {
            get { return (double) GetValue(ScaleYProperty); }
            set { SetValue(ScaleYProperty, value); }
        }

        public double DragAndDropDistance
        {
            get { return (double) GetValue(DragAndDropDistanceProperty); }
            set { SetValue(DragAndDropDistanceProperty, value); }
        }

        public bool DynamicAngleSelection
        {
            get { return (bool) GetValue(DynamicAngleSelectionProperty); }
            set { SetValue(DynamicAngleSelectionProperty, value); }
        }

        public double AnimateStep
        {
            get { return (double)GetValue(AnimateStepProperty); }
            set { SetValue(AnimateStepProperty, value); }
        }

        // Attached property getters and setters
        public static double GetAngle(DependencyObject obj)
        {
            return (double) obj.GetValue(AngleProperty);
        }

        public static void SetAngle(DependencyObject obj, double value)
        {
            obj.SetValue(AngleProperty, value);
        }

        public static DragState GetDragging(DependencyObject obj)
        {
            return (DragState) obj.GetValue(DraggingProperty);
        }

        public static void SetDragging(DependencyObject obj, DragState value)
        {
            obj.SetValue(DraggingProperty, value);
        }

        public static double GetLeft(DependencyObject obj)
        {
            return (double) obj.GetValue(LeftProperty);
        }

        public static void SetLeft(DependencyObject obj, double value)
        {
            obj.SetValue(LeftProperty,value);
        }

        public static double GetTop(DependencyObject obj)
        {
            return (double)obj.GetValue(TopProperty);
        }

        public static void SetTop(DependencyObject obj, double value)
        {
            obj.SetValue(TopProperty, value);
        }

        public static double GetDefaultLeft(DependencyObject obj)
        {
            return (double)obj.GetValue(DefaultLeftProperty);
        }

        public static void SetDefaultLeft(DependencyObject obj, double value)
        {
            obj.SetValue(DefaultLeftProperty, value);
        }

        public static double GetDefaultTop(DependencyObject obj)
        {
            return (double)obj.GetValue(DefaultTopProperty);
        }

        public static void SetDefaultTop(DependencyObject obj, double value)
        {
            obj.SetValue(DefaultTopProperty, value);
        }

        public static Point GetSourcePoint(DependencyObject obj)
        {
            return (Point) obj.GetValue(SourcePointProperty);
        }

        public static void SetSourcePoint(DependencyObject obj, Point point)
        {
            obj.SetValue(SourcePointProperty, point);
        }

        public static Point GetTargetPoint(DependencyObject obj)
        {
            return (Point)obj.GetValue(TargetPointProperty);
        }

        public static void SetTargetPoint(DependencyObject obj, Point point)
        {
            obj.SetValue(TargetPointProperty, point);
        }

        /// <summary>
        /// InvalidateLayoutCallback is used by the dependency properties to invalidate the arrangement when they are
        /// changed. It is not used for attached properties, as these tend to be set during an arrange, and so invalidating
        /// the arrangement during an arrangement causes a circular loop.
        /// </summary>
        static void InvalidateLayoutCallback(DependencyObject o, DependencyPropertyChangedEventArgs args)
        {
            RadialPanel p = o as RadialPanel;

            // Force the layout to change, which will recalculate the various positions of the panel items
            if (p != null)
            {
                p.InvalidateMeasure();
                p.InvalidateArrange();
            }
        }

        /// <summary>
        /// DoAnimationCallback is used when AnimateStep property is being animated, so that we can update the positions
        /// of items being animated.
        /// </summary>
        static void DoAnimationCallback(DependencyObject o, DependencyPropertyChangedEventArgs args)
        {
            RadialPanel p = o as RadialPanel;

            if (p != null)
            {
                p.DoAnimationStep((double) args.NewValue);
            }
        }

        #endregion  // Dependency Properties

        #region Events To Fire

        // Event handler for when an item passes from one ring to another
        public event ItemCrossedRingEventHandler ItemCrossedRing;        

        protected virtual void OnItemCrossedRing(ItemCrossedRingEventArgs args)
        {
            ItemCrossedRingEventHandler handler = ItemCrossedRing;

            if (handler != null)
            {
                handler(this, args);
            }
        }

        // Event handler for when an item begins dragging
        public event RoutedEventHandler BeginDrag;

        protected virtual void OnBeginDrag(RoutedEventArgs args)
        {
            RoutedEventHandler handler = BeginDrag;
   
            if (handler != null)
            {
                handler(this, args);
            }
        }

        // Event handler for when an item stops dragging (regardless of outcome)
        public event RoutedEventHandler EndDrag;

        protected virtual void OnEndDrag(RoutedEventArgs args)
        {
            RoutedEventHandler handler = EndDrag;

            if (handler != null)
            {
                handler(this, args);
            }
        }

        #endregion  // Events To Fire

        #region Event Handlers

        void RadialPanel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            // A mouse down has occurred - which of our radial items is selected, so we can decide if it is being dragged.
            _sourceObject = GetControlUnderMouse(e.GetPosition(this), null);

            if (_sourceObject != null)
            {
                // We found an item to think about dragging, but the user may also just be clicking the item
                // so now we capture the mouse, wait to see what happens when we get a mouse move event,
                // or a button up event
                if (CaptureMouse())
                {
                    _capturedMouse = true;                    
                    _sourcePoint = e.GetPosition(null);
                }
                else
                {
                    // Failed to capture the mouse, probably because we're not Enabled. For now, just pretend we haven't
                    // been clicked. Is there a decent way to indicate this to the user? Shake the radial control for example?
                    _sourceObject = null;
                }
            }
        }

        void RadialPanel_MouseMove(object sender, MouseEventArgs e)
        {
            // The mouse is being moved over the panel. If we've captured the mouse it means we're in a potential drag and drop
            // situation, so see what's going on.
            if (_capturedMouse && _sourceObject != null)
            {
                Point pt = e.GetPosition(null);

                // Have we decided if we're dragging a given object?
                if (((DragState) _sourceObject.GetValue(DraggingProperty))==DragState.Dragging)
                {
                    // We've decided we're dragging, so continue to do the drag work

                    // We update the position property to reflect the newly dragged to position, but we
                    // force an Arrange() of the item to ensure it moves without having to re-arrange all items.
                    double x = (double) _sourceObject.GetValue(LeftProperty);
                    double y = (double) _sourceObject.GetValue(TopProperty);
                    x += (pt.X - _sourcePoint.X)/ScaleX;
                    y += (pt.Y - _sourcePoint.Y)/ScaleY;
                    _sourceObject.SetValue(LeftProperty, x);
                    _sourceObject.SetValue(TopProperty, y);
                    _sourceObject.Arrange(new Rect(x, y, _sourceObject.Width, _sourceObject.Height));
                    _sourcePoint = pt;

                    RadialPanel panel = GetPanelUnderMouse(e);

                    if (panel != _lastPanelUnderMouse && _lastPanelUnderMouse != null)
                    {
                        // We've now dragged an item off the last panel, so make the items on the last panel reposition to reflect this
                        if (_lastPanelUnderMouse == this)
                        {
                            // We've moved off this panel, so re-position items to pretend the drag item is no longer on the panel
                            // and also pretend that there is one item less on the panel (which we'd have to do!)
                            _lastPanelUnderMouse.AnimateItemsToNewPosition(-1, Children.IndexOf(_sourceObject), -1);
                        }
                        else
                        {
                            // We've moved off another panel, which means we would have animated it like we'd added an item to it,
                            // therefore now just tell it to revert back to its default positions
                            _lastPanelUnderMouse.AnimateItemsToNewPosition(-1, -1, 0);
                        }
                    }
                    if (panel == this)
                    {
                        // Can we drop on an item near where we currently are?
                        RadialItemControl dropOnObject = GetNearestItemToPoint(e.GetPosition(this), (_lastPanelUnderMouse != null) ? _sourceObject : null);

                        if (dropOnObject != null)
                        {
                            // Are we highlighting a new item?
                            int dropOnIndex = Children.IndexOf(dropOnObject);

                            if (dropOnIndex != _lastHilightedIndex)
                            {
                                // We are highlighting a new item, so move the other items out of the way in an animation by pretending
                                // we're losing the item at the current source index, and adding it in at the highlighted index.
                                _lastHilightedIndex = dropOnIndex;
                                AnimateItemsToNewPosition(_lastHilightedIndex, Children.IndexOf(_sourceObject),0);
                            }
                        }
                        else
                        {
                            // We're not able to drop on an item - had we previously moved items out of the way because
                            // we could have dropped at that location?
                            if (_lastHilightedIndex != -1)
                            {
                                // We need to animate the objects back to their default location - specifying -1 for the insertion point\
                                // achieves that
                                _lastHilightedIndex = -1;
                                AnimateItemsToNewPosition(-1, -1,0);
                            }
                        }
                    }
                    else if (panel != null)
                    {
                        // We're dragging over a different panel
                        // Can we drop on an item near where we currently are?
                        int dropOnIndex = panel.GetNearestItemToPointWhenAdding(e.GetPosition(panel));

                        if (dropOnIndex != -1)
                        {
                            // Are we highlighting a new item?
                            if (dropOnIndex != panel.LastHilightedIndex)
                            {
                                // We are highlighting a new item, so move the other items out of the way in an animation, and depict the
                                // fact that we effectively have one extra item on that panel.
                                panel.LastHilightedIndex = dropOnIndex;
                                panel.AnimateItemsToNewPosition(panel.LastHilightedIndex, -1,1);
                            }
                        }
                        else
                        {
                            // We're not able to drop on an item - had we previously moved items out of the way because
                            // we could have dropped at that location?
                            if (panel.LastHilightedIndex != -1)
                            {
                                // We need to animate the objects back to their default location - specifying -1 for the insertion point
                                // achieves that
                                panel.LastHilightedIndex = -1;
                                panel.AnimateItemsToNewPosition(-1, -1,0);
                            }
                        }
                    }
                    _lastPanelUnderMouse = panel;
                }
                else
                {
                    // We haven't decided if we're dragging or not yet, and the button up hasn't been received
                    // so decide if we know what we're doing yet
                    RadialItemControl item = GetControlUnderMouse(e.GetPosition(this), null);

                    if (item == _sourceObject)
                    {
                        // We're still over the button that we originally clicked, so see if we've moved far enough away to start
                        // dragging yet
                        if (Math.Abs(pt.X-_sourcePoint.X) >= 2 || Math.Abs(pt.Y-_sourcePoint.Y) >= 2)
                        {
                            // We've moved far enough away (2 or more pixels), so are now in a drag situation
                            // Set all the items to be moveable so we can animate if necessary
                            foreach(UIElement childItem in Children)
                            {
                                childItem.SetValue(DraggingProperty,DragState.Animating);
                            }
                            // Tell the registered panels that we may want to animate them too
                            SetRegisteredPanelsDragState(DragState.Animating);
                            // Set the object being dragged to be in a dragging state so we can move it
                            _sourceObject.SetValue(DraggingProperty, DragState.Dragging);
                            // Hide the label while dragging
                            _sourceLabelVisibility = _sourceObject.Label.Opacity;
                            _sourceObject.Label.Opacity = 0;
                            _sourceObject.HandleBeginDrag();

                            // We update the position property to reflect the newly dragged to position, but we
                            // force an Arrange() of the item to ensure it moves without having to re-arrange all items.
                            double x = (double) _sourceObject.GetValue(LeftProperty);
                            double y = (double) _sourceObject.GetValue(TopProperty);
                            x += (pt.X - _sourcePoint.X) / ScaleX;
                            y += (pt.Y - _sourcePoint.Y) / ScaleY;
                            _sourceObject.SetValue(LeftProperty, x);
                            _sourceObject.SetValue(TopProperty, y);
                            _sourceObject.Arrange(new Rect(x,y,_sourceObject.Width, _sourceObject.Height));
                            _sourcePoint = pt;

                            OnBeginDrag(new RoutedEventArgs());
                        }
                    }
                    else
                    {
                        // We're over a different object, so just ignore it for now
                    }
                }                
            }
        }

        void RadialPanel_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            // If we captured the mouse, we were potentially in a drag operation. If we didn't capture the mouse
            // we don't care about this event.
            if (_capturedMouse && _sourceObject != null)
            {
                // Release capture.
                _capturedMouse = false;
                ReleaseMouseCapture();

                Point pt = e.GetPosition(null);

                // Are we dragging the current object?
                if (((DragState)_sourceObject.GetValue(DraggingProperty)) == DragState.Dragging)
                {
                    // We're finishing a drag operation, decide on how to move the dragged item

                    // Mark the item as non-dragging, for when we animate it at the end of the drag operation
                    _sourceObject.SetValue(DraggingProperty, DragState.Animating);

                    // Are we still dragging over our own panel, or over another panel on a window.
                    // We use our custom GetPanelUnderMouse() function to avoid some weird functionality in HitTest()
                    // where it can return 2 panels, even though they don't overlap
                    RadialPanel panel = GetPanelUnderMouse(e);

                    if (panel != null)
                    {
                        // We're over a panel, so this drag operation can be completed
                        if (panel == this)
                        {
                            // We're doing an internal drag to ourselves
                            // Get the object nearest to the current cursor position, excluding the object we're dragging
                            // (which will always be closest to the current cursor position!)
                            Point panelPt = e.GetPosition(panel);
                            RadialItemControl closestItem = GetNearestItemToPoint(panelPt, _sourceObject);

                            if (closestItem != null)
                            {
                                // We don't need to check the max items because we're dragging to the same panel
                                int indexOfClosestItem = Children.IndexOf(closestItem);
                                int indexOfDragItem = Children.IndexOf(_sourceObject);
                                UIElement tempItem = Children[indexOfDragItem];

                                Children.RemoveAt(indexOfDragItem);
                                Children.Insert(indexOfClosestItem, tempItem);
                            }
                        }
                        else
                        {
                            // We're dragging to a different panel
                            // Get the object nearest to the current cursor position, excluding the object we're dragging
                            // (which will always be closest to the current cursor position!)
                            Point panelPt = e.GetPosition(panel);
                            int indexOfClosestItem = panel.GetNearestItemToPointWhenAdding(panelPt);
                            // Also, get the offset of the mouse from the top of the item we're dragging, since we will need to update the
                            // Left/Top property of the object ready for animation when we insert the object into the new panel, and if we just
                            // set it to the mouse positioned relative to the new panel, then it will cause a slight jump caused by this offset of the mouse
                            Point deltaPt = e.GetPosition(_sourceObject);

                            if (panel.Children.Count < panel.MaxItems)
                            {
                                if (indexOfClosestItem != -1)
                                {
                                    // We need to remove the drag object from our current position, and then 
                                    // insert at the new position
                                    int indexOfDragItem = Children.IndexOf(_sourceObject);
                                    UIElement tempItem = Children[indexOfDragItem];

                                    Children.RemoveAt(indexOfDragItem);
                                    panel.Children.Insert(indexOfClosestItem, tempItem);
                                }
                                else
                                {
                                    // Moving to a different panel so add this to the panel, regardless of its location
                                    UIElement tempItem = _sourceObject;

                                    Children.Remove(_sourceObject);
                                    panel.Children.Add(tempItem);
                                }
                                // Left/Top of _sourceObject is still for the old panel, and if we're going to animate back, we need to change it
                                // to be relative to the new panel, taking into the account the offset of the mouse relative to the drag object
                                Point newPoint = e.GetPosition(panel);

                                _sourceObject.SetValue(LeftProperty, newPoint.X - deltaPt.X);
                                _sourceObject.SetValue(TopProperty, newPoint.Y - deltaPt.Y);
                            }
                        }
                    }

                    // We want to animate the items to the right position, e.g. if we dropped in no-man's land, we need to animate everything back
                    // to its default position. Becasuse of this, we have to wait for the animation to complete before we finish off our drag operation
                    // (e.g. setting all items back to Fixed, asserting angles, etc).
                    // FinishDragOperation() sorts all this out for us, but we need to do it on all panels we've messed about with.
                    // It will also clean up things like _sourceObject, hence the reason we don't do that here!
                    _draggedToPanel = panel;
                    foreach(RadialPanel item in _registeredPanels)
                        item.FinishDragOperation();
                    FinishDragOperation();                    
                }
                else
                {
                    // We're not in a drag so we might be clicking if the mouse is released over the same button
                    // as the one we began clicking
                    RadialItemControl item = GetControlUnderMouse(e.GetPosition(this), null);

                    if (item == _sourceObject)
                    {
                        // The mouse is being released over the same item as the one we started on, therefore it is being clicked
                        _sourceObject.HandleClicked();
                    }
                    _sourceObject = null;
                }                
                // Reset our internal drag state
                _lastHilightedIndex = -1;
                _lastPanelUnderMouse = null;
            }
        }

        #endregion // Event Handlers

        #region Member functions

        /// <summary>
        /// Registers the given panel with this class, so that we can perform inter-panel drag and drop. Note that if panel A registers
        /// with panel B, panel B will need to register with panel A (in other words, we assume only one way drag and drop, unless the caller
        /// decides to register the two way connection).
        /// </summary>
        /// <param name="panel">The panel to register as a "friend" of this panel.</param>
        public void RegisterPanel(ref RadialPanel panel)
        {
            // We cannot register ourselves.
            if (panel == this)
                return;
            // We shouldn't register if the panel is already registered
            if (_registeredPanels.Contains(panel))
                return;
            _registeredPanels.Add(panel);
        }

        /// <summary>
        /// Unregisters the given panel from this class, which will stop us dragging and dropping to that panel.
        /// </summary>
        /// <param name="panel">The panel to unregister</param>
        public void UnregisterPanel(ref RadialPanel panel)
        {
            if (_registeredPanels.Contains(panel))
            {
                _registeredPanels.Remove(panel);
            }
        }

        /// <summary>
        /// Prepares the radial item controls on the registered panels to be a given drag state, typically Animating or Fixed
        /// for when we are about to drag, and may need to animate items on remote panels.
        /// </summary>
        /// <param name="newDragState"></param>
        private void SetRegisteredPanelsDragState(DragState newDragState)
        {
            foreach(RadialPanel item in _registeredPanels)
            {
                foreach(UIElement child in item.Children)
                {
                    RadialItemControl control = child as RadialItemControl;

                    if (control != null)
                        control.SetValue(DraggingProperty,newDragState);
                }
            }
        }

        /// <summary>
        /// This function returns the given object type that is under the current mouse position, or null
        /// if the mouse is not over an item we're interested in. Note, it is not a good idea to use this, because in Silverlight 2
        /// beta 2, HitTest() doesn't seem to report what we would expect it to report, and the advancedCheck property to get round this
        /// didn't always work in the real world. Instead, use GetControlUnderMouse or GetPanelUnderMouse instead, which do simple
        /// bounds checking based hit tests.
        /// </summary>
        /// <param name="hittestObject">The UIElement at which to perform the HitTest at (null will use this panel)</param>
        /// <param name="pt">The current cursor position, relative to this panel</param>
        /// <param name="type">The type of UIElement that we wish to return</param>
        /// <param name="advancedCheck">Whether we do an extended check to see if the given point is within the rectangle
        /// of the item being hit tested. This assumes that items are laid out on a canvas, and is used to get round
        /// a problem in Silverlight where HitTest() would return two objects that didn't overlap, when only one
        /// object should have been returned!</param>
        /// <param name="ignoreObject">The control to ignore when getting the item under the mouse.</param>
        /// <returns>A UIElement which is the current object of given type that we are currently over</returns>
        private UIElement GetItemUnderMouse(UIElement hittestObject, Point pt, Type type, bool advancedCheck, RadialItemControl ignoreObject)
        {
            if (hittestObject == null)
                hittestObject = this;
            IEnumerable<UIElement> items = System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(pt, hittestObject) as List<UIElement>;

            //IEnumerable<UIElement> items = hittestObject.HitTest(pt);
            UIElement result = null;

            foreach (UIElement item in items)
            {
                if (result == null && (item != ignoreObject) && (item.GetType() == type))
                {
                    if (advancedCheck)
                    {
                        // Check that the rectangle bounding the UIElement contains our point
                        // Normally we wouldn't do this, but it's a fudge to get round an issue where Silverlight
                        // told us that an object not containing the point was a valid match!
                        // It also assumes that the objects are laid out on a canvas, which is really bad
                        double lx = (double) item.GetValue(Canvas.LeftProperty);
                        double ty = (double) item.GetValue(Canvas.TopProperty);
                        double rx = lx + ((FrameworkElement) item).Width;
                        double by = ty + ((FrameworkElement) item).Height;

                        if (pt.X > lx && pt.X < rx && pt.Y > ty && pt.Y < by)
                        {
                            // This is the item we're interested in
                            result = item;
                        }
                    }
                    else
                    {
                        // This is the item we're interested in
                        result = item;
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// GetControlUnderMouse decides which of our children are under the given mouse point. This was necessary because HitTest()
        /// would sometimes return 2 controls being under the mouse, even though they didn't overlap. This problem only occurred when
        /// the owning Silverlight container's root object had a ScaleTransform applied of ~1, and if one of the child control's images
        /// extended slightly beyond the control's boundary (although the image still didn't overlap with another child control).
        /// The implementation is a basic one, just getting the Left,Top,Width, and Height of each control, and seeing if the given 
        /// point is within that rectangle.
        /// </summary>
        /// <param name="pt">The point, relative to this panel, to look for a control (typically, you would pass mouseEvent.GetPosition(this)</param>
        /// <param name="ignoreObject">A RadialItemControl to ignore when looking for items under the given point.</param>
        /// <returns>The RadialItemControl child item under the given point, or null if no items are under the given point.</returns>
        private RadialItemControl GetControlUnderMouse(Point pt, RadialItemControl ignoreObject)
        {
            RadialItemControl result = null;

            foreach(UIElement item in Children)
            {
                RadialItemControl thisItem = item as RadialItemControl;

                // Check that we have a valid radial control, and that we haven't already got a result
                if (thisItem != null && result == null && thisItem != ignoreObject)
                {
                    // Is the point within the boundary of this object
                    Point ptLeftTop = new Point((double) thisItem.GetValue(LeftProperty), (double) thisItem.GetValue(TopProperty));
                    Point ptRightBottom = new Point(ptLeftTop.X + thisItem.Width, ptLeftTop.Y + thisItem.Height);

                    if (pt.X >= ptLeftTop.X && pt.X <= ptRightBottom.X && pt.Y >= ptLeftTop.Y && pt.Y <= ptRightBottom.Y)
                        result = thisItem;
                }
            }
            return result;
        }

        /// <summary>
        /// GetPanelUnderMouse works out which RadialPanel is currently located under the mouse. There is a bug in HitTest which can
        /// sometimes return 2 panels as being under the mouse, even though they don't overlap, so it was necessary to implement
        /// our own Panel hit testing code. Because we don't know what type of container the panels are stored in, we get the 
        /// current mouse position of each RadialPanel in the given container, and the first one that reports a position that
        /// falls within (0,0) to (Width,Height) is a panel containing the mouse.
        /// Note, we only check panels that have been registered with us, so that the user can control which panels we can drag and drop between
        /// </summary>
        /// <param name="e">The mouse event arguments from our caller - we use this to get the current mouse position relative to each child item</param>
        /// <returns>The radial panel currently under the mouse.</returns>
        private RadialPanel GetPanelUnderMouse(MouseEventArgs e)
        {
            RadialPanel result = null;

            // Check our registered panels to see if they fall under the cursor
            foreach(RadialPanel item in _registeredPanels)
            {
                if (item != null && result == null)
                {
                    Point ptThis = e.GetPosition(item);

                    if (ptThis.X >= 0 && ptThis.X <=item.Width && ptThis.Y >= 0 && ptThis.Y <= item.Height)
                    {
                        // This is the item under the mouse
                        result = item;
                    }
                }
            }
            if (result == null)
            {
                // Check 'this' panel too, in case the cursor is over it
                Point ptThis = e.GetPosition(this);

                if (ptThis.X >= 0 && ptThis.X <= Width && ptThis.Y >= 0 && ptThis.Y <= Height)
                {
                    // This is the item under the mouse
                    result = this;
                }
            }
            return result;
        }

        /// <summary>
        /// This function works out the distance from the given point to the center of each child item and decides
        /// which child item is closest, ignoring a given object passed as a parameter.
        /// </summary>
        /// <param name="pt">The point to compare relative distances with</param>
        /// <param name="ignoreThisObject">The RadialItemControl to ignore (typically the one being dragged)</param>
        /// <returns>The RadialItemControl nearest to the point, or null if no items are close enough. An item is
        /// only close enough if the distance between its center and the given point falls within a certain constant.
        /// The constant needs to be user-definable in future versiosns.</returns>
        public RadialItemControl GetNearestItemToPoint(Point pt, RadialItemControl ignoreThisObject)
        {
            // Enumerate the radial controls that belong to us, and find the nearest item which isn't the object to ignore!
            RadialItemControl bestMatchingObject = null;
            // We assume that the sun is going to be further away than any of our objects, so use the distance to the sun
            // as a maximum value for our initial nearest object distance. It is unlikely that monitor technology will exceed
            // this prior to the heat death of the universe.
            double shortestDistanceToObject = ApproxPixelsToSun;

            foreach(UIElement item in Children)
            {
                RadialItemControl control = item as RadialItemControl;
                
                if (control != null && control != ignoreThisObject)
                {
                    // How far away are we from the center of this item?
                    double cx = ((double) control.GetValue(DefaultLeftProperty)) + control.Width/2;
                    double cy = ((double) control.GetValue(DefaultTopProperty)) + control.Height/2;

                    double distance = Math.Sqrt(Math.Pow((pt.X - cx), 2) + Math.Pow((pt.Y - cy), 2));

                    if (distance < shortestDistanceToObject)
                    {
                        // This item is closer than our closest item so far, so store it as our best match thus far
                        bestMatchingObject = control;
                        shortestDistanceToObject = distance;
                    }
                }
            }
            // Only say an item is closest to point if it is within a certain distance, defined by the DragAndDropDistance property.            
            if (shortestDistanceToObject < DragAndDropDistance)
                return bestMatchingObject;
            return null;
        }

        /// <summary>
        /// Works out the best index to add an item to the panel, if we are dragging it from another panel. This is different to GetNearestItemToPoint
        /// which looks at the existing locations of all items, and decides which one the point is closest to. When we drag between panels however, 
        /// we will be adding a new item, which will affect the position of all items on the panel, so we need to find the closest item based on the 
        /// new points where items will be located.
        /// </summary>
        /// <param name="pt">The point to compare relative distances with</param>
        /// <returns>The index into Children where the new item would be inserted, or -1 if it can't be inserted</returns>
        public int GetNearestItemToPointWhenAdding(Point pt)
        {
            // Find the best angle at which to insert a new item, based on the mouse position
            int bestMatchingIndex = -1;
            int items = Children.Count + 1;

            if (items > MaxItems)
            {
                // There would be too many items on the container if we dropped it here
                return -1;
            }

            // We assume that the sun is going to be further away than any of our objects, so use the distance to the sun
            // as a maximum value for our initial nearest object distance. It is unlikely that monitor technology will exceed
            // this prior to the heat death of the universe.
            double shortestDistanceToObject = ApproxPixelsToSun;
            double angleEnd = ((AngleEnd % 360) == 0) ? 360 : (AngleEnd % 360);

            if (items > 1 && angleEnd < 360)
                items--;

            double angleStep = (angleEnd - AngleStart) / items;
            double crtAngle = AngleStart;
            
            for (int item = 0; item < items; ++item, crtAngle += angleStep)
            {
                double cx, cy;
                double radians = crtAngle*(Math.PI/180);

                cx = Center.X + (Math.Cos(radians)*Radius.X);
                cy = Center.Y + (Math.Sin(radians)*Radius.Y);

                double distance = Math.Sqrt(Math.Pow((pt.X - cx), 2) + Math.Pow((pt.Y - cy), 2));

                if (distance < shortestDistanceToObject)
                {
                    // This item is closer than our closest item so far, so store it as our best match thus far
                    bestMatchingIndex = item;
                    shortestDistanceToObject = distance;
                }
            }
            
            if (DynamicAngleSelection)
            {
                // If we're dynamically selecting angles (i.e. the distance between objects can changed depending on number of objects)
                // then we return the nearest item, regardless of whether it's close enough based on the DragAndDropDistance property
                return bestMatchingIndex;
            }
            else
            {
                // We're not dynamically selecting angles, so only return the best match if it is "close enough", otherwise say we
                // couldn't find anything. If we don't do this, then dropping an item far from other objects will still insert it within the
                // current list of objects, rather than add it at the end of the list, and the UX seems weird.
                if (shortestDistanceToObject < DragAndDropDistance)
                    return bestMatchingIndex;
            }
            return -1;
        }

        /// <summary>
        /// ArrangeOverride is called when our children need to be re-arranged. It is here where we decide the angle
        /// to position each child at. For simplicity we always say that we are sized based on our current width
        /// and height - this means that if the Width / Height property are not defined in the XAML, we will not
        /// appear. This could be improved in a future version.
        /// </summary>
        /// <returns>The final size of the panel, which will be no different from current Width / Height properties</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            // Calculate the correct angle for each item on the ellipse.

            // Spread the items out equally on the ring. If the angle >= 360, then we don't want the
            // last item to overlap the first item, but for any other value, we do want the last item to be positioned
            // at the maximum angle. To achieve this, we reduce the number of items by 1, if we are not filling
            // the entire circle with our items.   
            int items = (DynamicAngleSelection) ? Children.Count : MaxItems;
            double angleEnd = ((AngleEnd % 360) == 0) ? 360 : (AngleEnd % 360);
            if (items > 1 && angleEnd < 360)
                items--;
            double angleStep = (angleEnd - AngleStart)/items;
            double crtAngle = AngleStart;

            foreach(UIElement childItem in Children)
            {
                childItem.SetValue(AngleProperty, crtAngle);
                crtAngle += angleStep;
            }

            // Now ensure that the angles get converted into positions on the page
            doArrange();
            
            // We always return the fixed size of the panel, since we don't want it to alter based on how we've positioned
            // items. This does mean though that if the panel width or height has not been set, they are going to default to 0
            // and we're not going to display anything.
            return new Size(Width, Height);
        }

        /// <summary>
        /// doArrange does the work of positioning our children either at the given angle, or at given X,Y co-ordinate
        /// if the item is being dragged or animated.
        /// </summary>
        private void doArrange()
        {
            Point center = Center;
            Point radius = Radius;

            foreach (UIElement childItem in Children)
            {
                // UIElement have no width or height property, and the RenderSize and DesiredSize properties
                // return 0,0. To overcome this, we expect all of our items to be at least FrameworkElements
                // which do have the width and height property.
                FrameworkElement ctrlItem = childItem as FrameworkElement;

                if (ctrlItem != null)
                {
                    if (((DragState) ctrlItem.GetValue(DraggingProperty)) == DragState.Fixed)
                    {
                        // We're not dragging/animating this object, so position it based on its current angle,
                        // and then update its Position property to reflect where we've put it
                        double angle;
                        Point p;

                        // Get the angle that this item is on, then calculate the point that the item is located on 
                        // (why is this the top/left point, rather than the center?)
                        angle = (double) childItem.GetValue(AngleProperty);
                        angle = angle*(Math.PI/180);
                        p = new Point((Math.Cos(angle)*radius.X) + center.X,
                                      (Math.Sin(angle)*radius.Y) + center.Y);

                        ctrlItem.SetValue(LeftProperty, p.X - ctrlItem.Width/2);
                        ctrlItem.SetValue(TopProperty, p.Y - ctrlItem.Height/2);
                        // We update DefaultXXX properties here too, since they represent the position that an object
                        // will return to after it has animated.
                        ctrlItem.SetValue(DefaultLeftProperty, p.X - ctrlItem.Width / 2);
                        ctrlItem.SetValue(DefaultTopProperty, p.Y - ctrlItem.Height / 2);
                        // Position the object
                        ctrlItem.Arrange(
                            new Rect(p.X - ctrlItem.Width/2, p.Y - ctrlItem.Height/2, ctrlItem.Width, ctrlItem.Height));
                    }
                    else
                    {
                        // We are dragging this item, so arrange it at the current drag position
                        ctrlItem.Arrange(new Rect((double) ctrlItem.GetValue(LeftProperty), (double) ctrlItem.GetValue(TopProperty), ctrlItem.Width, ctrlItem.Height));
                    }
                }
            }
        }

        /// <summary>
        /// AnimateItemsToNewPosition will animate all non-dragged items based on where the current dragged item could
        /// be inserted (indexToInsert), so that the items appear as they would after the dragging item has been dropped.
        /// Currently there is a problem whereby the TargetProperty setting is not correct, and so the animation throws
        /// an exception. This code is currently not being called, and may need to be removed.
        /// </summary>
        /// <param name="indexToInsert">The index into Children where the dragged item would be inserted if dropped right now (-1 to animate back to default position)</param>
        /// <param name="indexDraggedFrom">The index into Children where the dragged item came from</param>
        /// <param name="deltaItemCount">The number to increment the actual item count by based on whether we'd be adding or removing items to the panel</param>
        public void AnimateItemsToNewPosition(int indexToInsert, int indexDraggedFrom, int deltaItemCount)
        {
            // Set up the animation and storyboard
            Storyboard story = new Storyboard();
            Duration durn = new Duration(TimeSpan.FromMilliseconds(250));

            story.Duration = durn;

            int items = 0;

            if (DynamicAngleSelection)
            {
                // If we're dragging from another panel, then we'll be adding this item to the list, so need to take into
                // acount one more child
                items = Children.Count + deltaItemCount;
            }
            else
            {
                // No dynamic selection, meaning that the angle between items is fixed based on the maximum number of items
                items = MaxItems;
            }

            double angleEnd = (AngleEnd == 0) ? 360 : AngleEnd;
            if (items > 1 && angleEnd < 360)
                items--;
            double angleStep = (angleEnd - AngleStart) / items;
            double crtAngle = AngleStart;
            double deltaAngle = 0;
            Point radius = Radius;
            Point center = Center;
            int itemIndex = 0;

            // Reposition all animating objects based on their potential new angle, which we calculate on the fly
            // as if the object being dragged were about to be dropped into Children at the indexToInsert position.
            foreach (UIElement childItem in Children)
            {
                RadialItemControl ctrlItem = childItem as RadialItemControl;

                if (ctrlItem != null)
                {
                    // Check to see whether or not we are hitting a hole caused by dragging from a position, or having to skip an angle
                    // because we've hit the insertion spot. We do this check twice; once before positioning, and once after,
                    // depending on whether we're dragging to a point earlier or later in the panel. For example, if we're dragging the
                    // the last item to the first item, the first item will need to move forward to make space, and we need to do that
                    // before we position the first item
                    if (indexDraggedFrom > indexToInsert || indexDraggedFrom == -1)
                    {
                        // If we're dragging to a point earlier in the panel, then when we reach the insertion point
                        // we need to move subsequent items one slot further
                        if (itemIndex == indexToInsert)
                            deltaAngle += angleStep;
                    }
                    else
                    {
                        // If we're dragging to a point later in the panel, then when we reach the dragging source point
                        // we need to move subsequent items one slot back.
                        if (itemIndex == indexDraggedFrom)
                            deltaAngle -= angleStep;    
                    }
                    
                    if (((DragState)ctrlItem.GetValue(DraggingProperty)) == DragState.Animating)
                    {
                        if (indexToInsert == -1 && indexDraggedFrom == -1)
                        {
                            // Return the items to their default position
                            Point ptSource = (Point) ctrlItem.GetValue(TargetPointProperty);
                            Point ptTarget = new Point((double) ctrlItem.GetValue(DefaultLeftProperty), (double) ctrlItem.GetValue(DefaultTopProperty));

                            ctrlItem.SetValue(SourcePointProperty, ptSource);
                            ctrlItem.SetValue(TargetPointProperty, ptTarget);
                            ctrlItem.SetValue(AngleProperty, crtAngle);
                        }
                        else
                        {
                            // This item is being animated (i.e. not at fixed angle, and not being dragged)
                            double x, y;
                            double radians = (crtAngle+deltaAngle)*(Math.PI/180);

                            x = (Math.Cos(radians)*radius.X) + center.X - ctrlItem.Width/2;
                            y = (Math.Sin(radians)*radius.Y) + center.Y - ctrlItem.Height/2;

                            Point sourcePoint =
                                new Point((double) ctrlItem.GetValue(LeftProperty),
                                          (double) ctrlItem.GetValue(TopProperty));
                            Point targetPoint = new Point(x, y);

                            ctrlItem.SetValue(SourcePointProperty, sourcePoint);
                            ctrlItem.SetValue(TargetPointProperty, targetPoint);
                            ctrlItem.SetValue(AngleProperty, (crtAngle + deltaAngle));

                            // The following is the old code that would be used to animate to a given location
                            // using the attached properties for Left and Top. A bug in Sl2 beta 2 meant that the
                            // property names could not be found, and so it was necessary to cheat, and use the
                            // Source, Target and AnimateStep (non-attached) properties. Have left this code here
                            // for when the bug is fixed, although bear in mind that this code is untested, and may
                            // need to be fixed. It also doesn't take into account what happens when indexToInsert = -1
                            // (meaning return the items to their default location).
#if CAN_ANIMATE_ATTACHED_PROP
                            DoubleAnimation animX = new DoubleAnimation();
                            DoubleAnimation animY = new DoubleAnimation();

                            animX.Duration = durn;
                            animX.To = x;
                            animY.Duration = durn;
                            animY.To = y;

                            story.Children.Add(animX);
                            story.Children.Add(animY);
                            Storyboard.SetTarget(animX, ctrlItem);
                            Storyboard.SetTargetProperty(animX, new PropertyPath("(RadialPanel.Left)"));
                            Storyboard.SetTarget(animY, ctrlItem);
                            Storyboard.SetTargetProperty(animY, new PropertyPath("(RadialPanel.Top)"));
#endif
                        }
                    }
                }
                if (indexDraggedFrom > indexToInsert || indexDraggedFrom == -1)
                {
                    // If we're dragging an item earlier in the panel then subsequent items need to move back a slot
                    // when we reach the slot we dragged from
                    if (itemIndex == indexDraggedFrom)
                        deltaAngle -= angleStep;
                }
                else
                {
                    // If we're dragging an item to later in the panel, then subsequent items need to move forward a slot
                    // when we reach the slot we're inserting at
                    if (itemIndex == indexToInsert)
                        deltaAngle += angleStep;            
                }
                
                crtAngle += angleStep;
                ++itemIndex;
            }

            // Now create a storyboard to animate the AnimateStep property from 0 to 1, which will animate the objects
            // from the Source property to the Target property of each animating control.
            DoubleAnimation animStep = new DoubleAnimation();

            animStep.Duration = durn;
            animStep.From = 0;
            animStep.To = 1;

            story.Children.Add(animStep);
            Storyboard.SetTarget(animStep, this);
            Storyboard.SetTargetProperty(animStep, new PropertyPath("AnimateStep"));

            story.Begin();
        }

        /// <summary>
        /// FinishDragOperation animates all the items on this panel to their default location, which we calculate in the usual way
        /// This is used when a drag operation ends and we either have to set everything back where it was because a drag was cancelled
        /// or tidily animate the dropped item to its real location (rather than where the user dropped it).
        /// </summary>
        protected void FinishDragOperation()
        {
            // Set up the animation and storyboard
            Storyboard story = new Storyboard();
            Duration durn = new Duration(TimeSpan.FromMilliseconds(250));

            story.Duration = durn;

            int items = (DynamicAngleSelection) ? Children.Count : MaxItems;
            double angleEnd = (AngleEnd == 0) ? 360 : AngleEnd;
            if (items > 1 && angleEnd < 360)
                items--;
            double angleStep = (angleEnd - AngleStart) / items;
            double crtAngle = AngleStart;
            double deltaAngle = 0;
            Point radius = Radius;
            Point center = Center;
            int itemIndex = 0;

            // Reposition all animating objects based on their potential new angle, which we calculate on the fly
            // as if the object being dragged were about to be dropped into Children at the indexToInsert position.
            foreach (UIElement childItem in Children)
            {
                RadialItemControl ctrlItem = childItem as RadialItemControl;

                if (ctrlItem != null)
                {
                    // Only do animations for RadialItemControls - even the Dragging item will be marked as Animating by this time
                    if (((DragState)ctrlItem.GetValue(DraggingProperty)) == DragState.Animating)
                    {
                        // Animate from the current Left/Top property, which is where we last moved the item to
                        Point ptSource = new Point((double)ctrlItem.GetValue(LeftProperty), (double)ctrlItem.GetValue(TopProperty));
                        // Animate to the appropriate location for the current angle
                        Point ptTarget;
                        double x, y;
                        double radians = (crtAngle + deltaAngle) * (Math.PI / 180);

                        x = (Math.Cos(radians) * radius.X) + center.X - ctrlItem.Width / 2;
                        y = (Math.Sin(radians) * radius.Y) + center.Y - ctrlItem.Height / 2;

                        ptTarget = new Point(x, y);

                        ctrlItem.SetValue(SourcePointProperty, ptSource);
                        ctrlItem.SetValue(TargetPointProperty, ptTarget);
                        // We assert the angle here just in case too, otherwise the label will suddenly jump when we next call InvalidateArrange()
                        ctrlItem.SetValue(AngleProperty, crtAngle);
                    }
                }

                crtAngle += angleStep;
                ++itemIndex;
            }

            // Now create a storyboard to animate the AnimateStep property from 0 to 1, which will animate the objects
            // from the Source property to the Target property of each animating control.
            DoubleAnimation animStep = new DoubleAnimation();

            animStep.Duration = durn;
            animStep.From = 0;
            animStep.To = 1;

            story.Children.Add(animStep);
            Storyboard.SetTarget(animStep, this);
            Storyboard.SetTargetProperty(animStep, new PropertyPath("AnimateStep"));

            // When the animation finishes we need to tidy up after the drag operation
            story.Completed += FinishDragAnimation_Completed;
            story.Begin();
        }

        /// <summary>
        /// This event is used when the final animation at the end of a drag is completed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void FinishDragAnimation_Completed(object sender, EventArgs e)
        {
            if (_sourceObject != null)
            {
                // Re-asserting the label's previous visible state (opacity) must be done before we say the item has moved panels
                // in case the parent wants to change visibility based on the panel moved to. Note, if we try and do this using
                // the Visibility property, we end up with the hidden label not being re-arranged on InvalidateArrange(), and so 
                // we switched to using Opacity instead
                _sourceObject.Label.Opacity = _sourceLabelVisibility;

                if (this != _draggedToPanel && _draggedToPanel != null)
                {
                    // Now fire event to say we've switched panels, since we dragged to a different panel.
                    ItemCrossedRingEventArgs args = new ItemCrossedRingEventArgs();

                    args.ItemMoved = _sourceObject;
                    args.FromRing = this;
                    args.ToRing = _draggedToPanel;
                    OnItemCrossedRing(args);
                }
            }

            // Make sure all items are marked as non-dragging/animated now
            foreach (UIElement childItem in Children)
            {
                childItem.SetValue(DraggingProperty, DragState.Fixed);
            }

            // If 'this' has a valid source object, then we were the source of the drag operation
            // so must fire the events to say the drag has ended
            if (_sourceObject != null)
            {
                // Make sure that all registered panels know they're no longer animating - this may not be necessary
                // since we call FinishDragOperation on all registered panels anyway
                SetRegisteredPanelsDragState(DragState.Fixed);
                _sourceObject.HandleEndDrag();
                OnEndDrag(new RoutedEventArgs());

            }
            // Now invalidate everything to make absolutely sure everything is where it is meant to be
            if (this != _draggedToPanel && _draggedToPanel != null)
            {
                _draggedToPanel.InvalidateMeasure();
                _draggedToPanel.InvalidateArrange();
            }
            InvalidateMeasure();
            InvalidateArrange();

            // Tidy up
            _sourceObject = null;
            _draggedToPanel = null;
        }

        /// <summary>
        /// DoAnimationStep is part of the cheat to get round a problem in Sl2 beta 2, whereby attached properties
        /// could not be animated. To work around this, we animate an AnimateStep property which calls the DoAnimationCallback
        /// every time that AnimateStep is changed. This updates the Left/Top property of any animating child object
        /// so that its Left/Top properties are part way between the Source and Target properties against the given child.
        /// </summary>
        /// <param name="step">How far, from 0 to 1, we are between animating from Source property to Target property</param>
        public void DoAnimationStep(double step)
        {
            foreach (UIElement item in Children)
            {
                RadialItemControl control = item as RadialItemControl;

                if (control != null)
                {
                    // Ensure we only animate objects that are in the Animating state.
                    if (((DragState) control.GetValue(DraggingProperty)) == DragState.Animating)
                    {
                        Point sourcePoint = (Point) control.GetValue(SourcePointProperty);
                        Point targetPoint = (Point) control.GetValue(TargetPointProperty);
                        Point newPoint = new Point();

                        // Calculate the new position for the control, and then move it there
                        newPoint.X = sourcePoint.X + (targetPoint.X - sourcePoint.X)*step;
                        newPoint.Y = sourcePoint.Y + (targetPoint.Y - sourcePoint.Y)*step;
                        control.SetValue(LeftProperty, newPoint.X);
                        control.SetValue(TopProperty, newPoint.Y);
                        //control.SetValue(AngleProperty, control.GetValue(AngleProperty));
                        control.Arrange(new Rect(newPoint.X,newPoint.Y,control.Width,control.Height));
                    }
                }
            }
        }

        #endregion  // Member functions

        #region Private variables

        // _sourceObject is the RadialItemControl that we are either trying to click, or drag
        private RadialItemControl _sourceObject;
        // _capturedMouse is whether we've captured mouse control, so we know to free it up later and also
        // decide whether we've already started processing a drag or click
        private bool _capturedMouse;
        // _sourcePoint is the point, relative to this panel, where we started the drag.
        private Point _sourcePoint;
        // _sourceLabelVisibility depicts the visibility (opacity property) of the source object's label when we started to drag it
        // We use opacity rather than the Visibility property, because hiding the label during an animation could cause it to appear in 
        // a weird position, since it's lack of visibility caused RadialItemPanel to not arrange it correctly.
        private double _sourceLabelVisibility;
        // _lastHighlightedIndex is the index of the item which we could drop our current drag item on to enable
        // us to work out if we need to animate items out of the way (-1 for not in use)
        private int _lastHilightedIndex;
        // _registeredPanels is the list of panels associated with this panel, that we can drag between. This affects
        // animation between panels.
        private List<RadialPanel> _registeredPanels;
        // _lastPanelUnderMouse - The last panel under the mouse during a drag operation
        private RadialPanel _lastPanelUnderMouse;
        // _draggedToPanel is the panel we dragged to on successful drag completion, for after drag animation
        private RadialPanel _draggedToPanel;

        #endregion // Private variables
    }
}

